

#include "marchingcubes.h"
#include "marchingcubes_lookups.h"

#include <GMColor.h>

MarchingCubes::MarchingCubes()
{
	fTargetValue = 0.5f;
	fStepSize = 1.0f;
	old_frame = -1;
	active_frame = 0;
	_vertex_buffer = 0x0;
	_element_buffer = 0x0;
}

void check_gl_errors()
{
	// EOF frame, check for errors
	GLenum errCode;
	errCode = glGetError();
	if( errCode != GL_NO_ERROR)
	{
		std::cout << "GL errors this frame: " << std::endl;
		//while (errCode != GL_NO_ERROR)
		{
			std::cout << gluErrorString(errCode) << std::endl;
			errCode = glGetError();
		}
	}

}

void check_gl_errors_without_glu()
{
	bool checked_all_errors = false;
	std::string gl_error_list = "";
	int num_errors = 0;

	while( !checked_all_errors )
	{
		int error_code = glGetError();
		if(error_code == GL_NO_ERROR)
		{
			checked_all_errors = true;
		}else{
			num_errors++;
			if(error_code == GL_INVALID_ENUM) gl_error_list += " GL_INVALID_ENUM ";
			if(error_code == GL_INVALID_VALUE) gl_error_list += " GL_INVALID_VALUE ";
			if(error_code == GL_INVALID_OPERATION) gl_error_list += " GL_INVALID_OPERATION ";
			if(error_code == GL_STACK_OVERFLOW) gl_error_list += " GL_STACK_OVERFLOW ";
			if(error_code == GL_STACK_UNDERFLOW) gl_error_list += " GL_STACK_UNDERFLOW ";
			if(error_code == GL_OUT_OF_MEMORY) gl_error_list += " GL_OUT_OF_MEMORY ";
			if(error_code == GL_TABLE_TOO_LARGE) gl_error_list += " GL_TABLE_TOO_LARGE ";
		}

		if( gl_error_list != "" )
		{
			std::cout << "gl errors this frame: " << gl_error_list << std::endl;
		}
	}
}


float MarchingCubes::fSample(int x, int y, int z)
{
	if( x >= 0 && y >= 0 && z >= 0 ){
		if( x < iDimX && y < iDimY && z < iDimZ ){
			return (float)_f3d_data->getFractionOfFluid().at( active_frame*tot_grid_cells + x + y*iDimX + z*iDimX*iDimY );
		}
	}

	return 0.0;
}


//fGetOffset finds the approximate point of intersection of the surface
// between two points with the values fValue1 and fValue2
GLfloat MarchingCubes::fGetOffset(float fValue1, float fValue2, float fValueDesired)
{
	GLfloat fDelta = fValue2 - fValue1;

	if(fDelta == 0.0)
	{
		return 0.5;
	}
	return (fValueDesired - fValue1)/fDelta;
}


//vGetColor generates a color from a given position and normal of a point
void MarchingCubes::vGetColor(GLvector &rfColor, GLvector &rfPosition, GLvector &rfNormal)
{
	//float fX = rfNormal.fX;
	//float fY = rfNormal.fY;
	//float fZ = rfNormal.fZ;
	//rfColor.fX = (fX > 0.0f ? fX : 0.0f) + (fY < 0.0f ? -0.5f*fY : 0.0f) + (fZ < 0.0f ? -0.5f*fZ : 0.0f);
	//rfColor.fY = (fY > 0.0f ? fY : 0.0f) + (fZ < 0.0f ? -0.5f*fZ : 0.0f) + (fX < 0.0f ? -0.5f*fX : 0.0f);
	//rfColor.fZ = (fZ > 0.0f ? fZ : 0.0f) + (fX < 0.0f ? -0.5f*fX : 0.0f) + (fY < 0.0f ? -0.5f*fY : 0.0f);

	GMlib::Color red = GMlib::GMcolor::Red;
	GMlib::Color indigo = GMlib::GMcolor::Indigo;
	//GMlib::Color resultColor = red.getInterpolatedHSV( fSample(rfPosition.fX, rfPosition.fY, rfPosition.fZ), blue);
	//GMlib::Color resultColor = red.getInterpolatedHSV( _f3d_data->getAbsVelocityMagnitudeAt( active_frame, (int)rfPosition.fX, (int)rfPosition.fY, (int)rfPosition.fZ), blue);
	GMlib::Color resultColor = indigo.getInterpolatedHSV( _f3d_data->getAbsVelocityMagnitudeAt( active_frame, rfPosition.fX, rfPosition.fY, rfPosition.fZ), red);
	rfColor.fX = resultColor.getRedC(); 
	rfColor.fY = resultColor.getGreenC(); 
	rfColor.fZ = resultColor.getBlueC(); 
	
}

void MarchingCubes::vNormalizeVector(GLvector &rfVectorResult, GLvector &rfVectorSource)
{
	float fOldLength;
	float fScale;

	fOldLength = sqrtf( (rfVectorSource.fX * rfVectorSource.fX) +
		(rfVectorSource.fY * rfVectorSource.fY) +
		(rfVectorSource.fZ * rfVectorSource.fZ) );

	if(fOldLength == 0.0)
	{
		rfVectorResult.fX = rfVectorSource.fX;
		rfVectorResult.fY = rfVectorSource.fY;
		rfVectorResult.fZ = rfVectorSource.fZ;
	}
	else
	{
		fScale = 1.0/fOldLength;
		rfVectorResult.fX = rfVectorSource.fX*fScale;
		rfVectorResult.fY = rfVectorSource.fY*fScale;
		rfVectorResult.fZ = rfVectorSource.fZ*fScale;
	}
}



//vGetNormal() finds the gradient of the scalar field at a point
//This gradient can be used as a very accurate vertex normal for lighting calculations
void MarchingCubes::vGetNormal(GLvector &rfNormal, float fX, float fY, float fZ)
{
	rfNormal.fX = fSample(fX-0.01f, fY, fZ) - fSample(fX+0.01f, fY, fZ);
	rfNormal.fY = fSample(fX, fY-0.01f, fZ) - fSample(fX, fY+0.01f, fZ);
	rfNormal.fZ = fSample(fX, fY, fZ-0.01f) - fSample(fX, fY, fZ+0.01f);
	vNormalizeVector(rfNormal, rfNormal);
}


//vMarchCube1 performs the Marching Cubes algorithm on a single cube
void MarchingCubes::MarchCube1(float fX, float fY, float fZ, float fScale)
{
	float drawScale = 0.1f;
	extern GLint aiCubeEdgeFlags[256];
	extern GLint a2iTriangleConnectionTable[256][16];

	GLint iCorner, iVertex, iVertexTest, iEdge, iTriangle, iFlagIndex, iEdgeFlags;
	GLfloat fOffset;
	GLvector sColor;
	GLfloat afCubeValue[8];
	GLvector asEdgeVertex[12];
	GLvector asEdgeNorm[12];
	GLvector marchPoint;
	marchPoint.fX = fX;
	marchPoint.fY = fY;
	marchPoint.fZ = fZ;

	//Make a local copy of the values at the cube's corners
	for(iVertex = 0; iVertex < 8; iVertex++)
	{
		afCubeValue[iVertex] = fSample(fX + a2fVertexOffset[iVertex][0],
			fY + a2fVertexOffset[iVertex][1],
			fZ + a2fVertexOffset[iVertex][2]);
	}

	//Find which vertices's are inside of the surface and which are outside
	iFlagIndex = 0;
	for(iVertexTest = 0; iVertexTest < 8; iVertexTest++)
	{
		if(afCubeValue[iVertexTest] <= fTargetValue) 
			iFlagIndex |= 1<<iVertexTest;
	}

	//Find which edges are intersected by the surface
	iEdgeFlags = aiCubeEdgeFlags[iFlagIndex];

	//If the cube is entirely inside or outside of the surface, then there will be no intersections
	if(iEdgeFlags == 0) 
	{
		return;
	}

	//Find the point of intersection of the surface with each edge
	//Then find the normal to the surface at those points
	for(iEdge = 0; iEdge < 12; iEdge++)
	{
		//if there is an intersection on this edge
		if(iEdgeFlags & (1<<iEdge))
		{
			fOffset = fGetOffset(afCubeValue[ a2iEdgeConnection[iEdge][0] ], 
				afCubeValue[ a2iEdgeConnection[iEdge][1] ], fTargetValue);

			asEdgeVertex[iEdge].fX = fX + (a2fVertexOffset[ a2iEdgeConnection[iEdge][0] ][0]  +  fOffset * a2fEdgeDirection[iEdge][0]) * fScale;
			asEdgeVertex[iEdge].fY = fY + (a2fVertexOffset[ a2iEdgeConnection[iEdge][0] ][1]  +  fOffset * a2fEdgeDirection[iEdge][1]) * fScale;
			asEdgeVertex[iEdge].fZ = fZ + (a2fVertexOffset[ a2iEdgeConnection[iEdge][0] ][2]  +  fOffset * a2fEdgeDirection[iEdge][2]) * fScale;

			vGetNormal(asEdgeNorm[iEdge], asEdgeVertex[iEdge].fX, asEdgeVertex[iEdge].fY, asEdgeVertex[iEdge].fZ);
		}
	}


	//Draw the triangles that were found.  There can be up to five per cube
	for(iTriangle = 0; iTriangle < 5; iTriangle++)
	{
		if(a2iTriangleConnectionTable[iFlagIndex][3*iTriangle] < 0)
			break;

		for(iCorner = 0; iCorner < 3; iCorner++)
		{
			iVertex = a2iTriangleConnectionTable[iFlagIndex][3*iTriangle+iCorner];

			vGetColor(sColor, asEdgeVertex[iVertex], asEdgeNorm[iVertex]); // or position as vertex position: asEdgeVertex[iVertex]

			_color_data.push_back( sColor.fX ); 
			_color_data.push_back( sColor.fY ); 
			_color_data.push_back( sColor.fZ ); 

			_normal_data.push_back( asEdgeNorm[iVertex].fX * drawScale ); 
			_normal_data.push_back( asEdgeNorm[iVertex].fY * drawScale ); 
			_normal_data.push_back( asEdgeNorm[iVertex].fZ * drawScale );

			_vertex_data.push_back( asEdgeVertex[iVertex].fX * drawScale ); 
			_vertex_data.push_back( asEdgeVertex[iVertex].fY * drawScale ); 
			_vertex_data.push_back( asEdgeVertex[iVertex].fZ * drawScale );


			
			num_triangles++;
		}
	}
}


//vMarchingCubes iterates over the entire dataset, calling vMarchCube on each cube
void MarchingCubes::buildIsoGeometry()
{
	_vertex_data.clear();
	_normal_data.clear();
	_color_data.clear();
	
	num_triangles = 0;

	for(int iX = 0; iX < iDimX; iX++)
	for(int iY = 0; iY < iDimY; iY++)
	for(int iZ = 0; iZ < iDimZ; iZ++)
	{
		MarchCube1(iX, iY, iZ, 1);
	}

	//_vbo = createVBO(&_vertex_data[0],sizeof(GLfloat) * _vertex_data.size(),GL_ARRAY_BUFFER,GL_STATIC_DRAW);
	//glDeleteBuffersARB(1, &_vertex_buffer);
	//glDeleteBuffersARB(1, &_element_buffer);

}


void MarchingCubes::setData( Flow3DData * _f3d_data )
{
	this->_f3d_data = _f3d_data;

	this->iDimX = _f3d_data->getGridDimensions()[0];
	this->iDimY = _f3d_data->getGridDimensions()[1];
	this->iDimZ = _f3d_data->getGridDimensions()[2];
	tot_grid_cells = iDimX*iDimY*iDimZ;
	//_vertex_data.reserve( 3 * tot_grid_cells );
}



void MarchingCubes::localDisplay()
{
	if( active_frame != old_frame )
	{
		old_frame = active_frame;
		buildIsoGeometry();
	}

	if(num_triangles <= 3 ) return;

	glCullFace(GL_BACK);
	//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

	const int immidiate = 0;
	const int vertexBufferObject = 1;
	const int vertexArrays = 2;

	int renderMode = vertexArrays;

	switch(renderMode)
	{
	case immidiate:
		glBegin(GL_TRIANGLES);
		for(size_t i=0; i < _vertex_data.size(); i+=3)
		{
			glVertex3f( _vertex_data[i+0], _vertex_data[i+1], _vertex_data[i+2] ); 
		}
		glEnd();
		break;
	case vertexBufferObject:
		break;
	case vertexArrays:

		glEnableClientState(GL_NORMAL_ARRAY);
		glEnableClientState(GL_COLOR_ARRAY);
		glEnableClientState(GL_VERTEX_ARRAY);
		

		glNormalPointer(GL_FLOAT, 0, &_normal_data[0] );
		glColorPointer(3,GL_FLOAT,0, &_color_data[0] );
		glVertexPointer(3,GL_FLOAT,0, &_vertex_data[0] );

		glDrawArrays(GL_TRIANGLES, 0, _vertex_data.size()/3 );

		glDisableClientState(GL_VERTEX_ARRAY);
		glDisableClientState(GL_COLOR_ARRAY);
		glDisableClientState(GL_NORMAL_ARRAY);
		break;
	default:
		break;
	}
}

void MarchingCubes::setFrame( int frame )
{
	this->active_frame = frame;
}

GLuint MarchingCubes::createVBO( const void* data, int dataSize, GLenum target, GLenum usage )
{
	GLuint id = 0;  // 0 is reserved, glGenBuffersARB() will return non-zero id if success

	glGenBuffers(1, &id);                        // create a vbo
	glBindBuffer(target, id);                    // activate vbo id to use
	glBufferData(target, dataSize, data, usage); // upload data to video card

	// check data size in VBO is same as input array, if not return 0 and delete VBO
	int bufferSize = 0;
	glGetBufferParameteriv(target, GL_BUFFER_SIZE_ARB, &bufferSize);

	std::cout << "made vbo, size: << " << bufferSize/1024 << " kb " << std::endl;

	if(dataSize != bufferSize)
	{
		glDeleteBuffers(1, &id);
		id = 0;
		std::cout << "[createVBO()] Data size is mismatch with input array\n";
	}

	return id;      // return VBO id
}




